
/**
 * Represents a k-dim box encapsulating a many sets of context characteristics.
 * 
 * @author Ed Hong
 *
 */
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import java.util.Collections;

public class ContextCharacteristicsBox {
        static boolean DEBUG = false;

	/** Stores context characteristic Box endpoints */
	private ContextCharacteristics lower;
	private ContextCharacteristics upper;
	
        public ContextCharacteristicsBox( ContextCharacteristics lower, ContextCharacteristics upper) {
            this.lower = lower;
            this.upper = upper;
            // make sure each lower coordinate is < each upper coordinate
            ContextDefinition cxtdef = ContextDefinition.getContextDefinition();
            int numcxt = cxtdef.getNumContexts();
            for (int i=0; i<numcxt; i++) {
                if (lower.getBucketID(i) > upper.getBucketID(i)) {
                    throw new IllegalArgumentException("ContextBox: contextid #" + i + "needs to be switched.");
                }
            }

        }
        
        public static ContextCharacteristicsBox getLargestBox() {
                ContextCharacteristics low, high;
                low = new ContextCharacteristics();
                high = new ContextCharacteristics();
                
                ContextDefinition cxtdef = ContextDefinition.getContextDefinition();
                int numcxt = cxtdef.getNumContexts();
		for (int i=0; i < numcxt; i++) {
			Context context = cxtdef.getContext(i);
			high.setBucketID(context.getContextID(), context.getNumberBuckets()-1);			
		}
               
                return new ContextCharacteristicsBox(low,high);
   
        }
	
	@Override
	public int hashCode() {
		return lower.hashCode()*(1<<16 -11) + upper.hashCode();
	}
        
        public boolean isSingletonBox() {
            return lower.equals(upper);
        }
	
        public ContextCharacteristics getlower() {
            return lower;
        }
        
        /**
         *  return split of input box into two halves as array of two boxes. 
         */
        public ContextCharacteristicsBox[] split(SplitCriteria ca) {
            ContextCharacteristicsBox[] retval;
            retval = new ContextCharacteristicsBox[2];
            ContextCharacteristics lowermid = new ContextCharacteristics(lower);
            ContextCharacteristics uppermid = new ContextCharacteristics(upper);
            if (lower.getBucketID(ca.getContextID())> ca.getBucketID()) 
                throw new IllegalArgumentException("ContextBoxsplit: invalid split on contextid #" + ca.getContextID());
            if (upper.getBucketID(ca.getContextID())<= ca.getBucketID()) 
                throw new IllegalArgumentException("ContextBoxsplit: invalid split on contextid #" + ca.getContextID());
            lowermid.setBucketID(ca.getContextID(), ca.getBucketID()+1);
            uppermid.setBucketID(ca.getContextID(), ca.getBucketID());
            retval[0] = new ContextCharacteristicsBox(lower, uppermid);
            retval[1] = new ContextCharacteristicsBox(lowermid, upper);            
            return retval;
        }
        
        /**
         *  return iterator over all feasible splits inside box (in standard order)
         */
         public Iterator<SplitCriteria> getSplitIterator() {
            return getSplitIterator(null);         
         }

        /**
         *  return iterator over all feasible splits inside box in random order, 
         *  with random bits specified by Random object; does not shuffle
         *  if Random == null;
         */
         public Iterator<SplitCriteria> getSplitIterator(Random r) {
            ArrayList<SplitCriteria> criteria = new ArrayList<SplitCriteria>();
            ContextDefinition cxtdef = ContextDefinition.getContextDefinition();
            int numcxt = cxtdef.getNumContexts();
            for (int i=0; i<numcxt; i++) {
			// total number of split criteria should be one less than the
			// number of buckets
			for (int j=lower.getBucketID(i); j < upper.getBucketID(i); j++) {
				criteria.add(new SplitCriteria(i, j));
			}
            }
            if (r != null) 
                Collections.shuffle(criteria,r);
//            System.out.println(criteria);
            return criteria.iterator();
         
         }


        /***
         * finds and returns smallest box containg all data in c; if c is empty, returns null. 
         */
        public static ContextCharacteristicsBox findSmallestEnclosingBox(ContextDataCollection c) {
            if (c.size()==0) return null;
            
            ContextDefinition cxtdef = ContextDefinition.getContextDefinition();
            int numcxt = cxtdef.getNumContexts();
            
            ContextCharacteristics low, high;
            
            Iterator<ContextCharacteristics> ccs = c.ccIterator();
            ContextCharacteristics initial = ccs.next();
            low = new ContextCharacteristics(initial);
            high = new ContextCharacteristics(initial);
            
            while (ccs.hasNext()) {
                ContextCharacteristics cc = ccs.next();
		for (int i=0; i < numcxt; i++) {
                    int bucketid = cc.getBucketID(i);
                    if (bucketid < low.getBucketID(i))
                        low.setBucketID(i,bucketid);
                    else if (bucketid > high.getBucketID(i))
                        high.setBucketID(i,bucketid);
		}
            }
            return new ContextCharacteristicsBox(low,high);
        }
        
	/**
	 * Two Boxes are equal only if their upper and lower endpoints are the same
	 */
	@Override
	public boolean equals(Object obj) {
		boolean result = false;
		if (obj != null) {
			if (obj.getClass() == getClass()) {
                            ContextCharacteristicsBox other = (ContextCharacteristicsBox) obj;
				return  upper.equals(other.upper) && lower.equals(other.lower);
			}
		}
      		return result;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
                sb.append("(lower=");
                sb.append(lower.toString());
                sb.append(",upper=");
                sb.append(upper.toString());
                sb.append(")");
		return sb.toString();
	}
}
